/*
 * Copyright, Aspect Security, Inc.
 *
 * This file is part of JavaSnoop.
 *
 * JavaSnoop is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JavaSnoop is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JavaSnoop.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.aspect.snoop.ui.hook;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.List;

import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.ListCellRenderer;

import org.jdesktop.application.Action;

import com.aspect.snoop.agent.SnoopAgent;
import com.aspect.snoop.ui.choose.clazz.ChooseClassView;
import com.aspect.snoop.util.ReflectionUtil;
import com.aspect.snoop.util.UIUtil;

public class AddFunctionHookView extends javax.swing.JDialog {

    private static AccessibleObject selectedMethod;
    private static Class selectedClass;
    private static Class[] parameterTypes;
    private static Class returnType;

    private static boolean shouldInherit;

    private Class currentClass;
    private Method[] loadedMethods;
    private Constructor[] loadedConstructors;

    public boolean getShouldInherit() {
        return shouldInherit;
    }

    public Class getSelectedClass() {
        return selectedClass;
    }

    public AccessibleObject getSelectedMethod() {
        return selectedMethod;
    }

    public Class[] getParameterTypes() {
        return parameterTypes;
    }

    public AddFunctionHookView(java.awt.Frame parent, boolean modal, String classpath) {

        super(parent, modal);
        initComponents();

        lstMethods.setCellRenderer( new ListCellRenderer() {

            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {

                JLabel lbl = new JLabel();
                if ( isSelected ) {
                    lbl.setForeground(Color.white);
                    lbl.setBackground(Color.blue);
                    lbl.setOpaque(true);
                }

                Member m = (Member)value;

                String toShow = ReflectionUtil.getMethodDescription(m);
                lbl.setText(" " + toShow);

                return lbl;
            }
            }
         );

        lstMethods.setListData(new Method[]{});

        selectedClass = null;
        selectedMethod = null;
        parameterTypes = null;
        returnType = null;

        lstMethods.addMouseListener(
                new MouseListener() {

                    public void mouseClicked(MouseEvent e) {
                        if (e.getClickCount() == 2) {
                            // user double clicked an item selection
                            finalizeSelection();
                            dispose();
                        }
                    }

                    public void mousePressed(MouseEvent e) {
                    }

                    public void mouseReleased(MouseEvent e) {
                    }

                    public void mouseEntered(MouseEvent e) {
                    }

                    public void mouseExited(MouseEvent e) {
                    }
                });
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jLabel1 = new javax.swing.JLabel();
        txtClass = new javax.swing.JTextField();
        btnBrowseForClass = new javax.swing.JButton();
        jLabel2 = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        lstMethods = new javax.swing.JList();
        btnAddWatch = new javax.swing.JButton();
        chkShouldInherit = new javax.swing.JCheckBox();
        btnSearchForFunction = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(com.aspect.snoop.JavaSnoop.class).getContext().getResourceMap(AddFunctionHookView.class);
        setTitle(resourceMap.getString("Form.title")); // NOI18N
        setModalityType(java.awt.Dialog.ModalityType.APPLICATION_MODAL);
        setName("Form"); // NOI18N

        jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
        jLabel1.setName("jLabel1"); // NOI18N

        txtClass.setText(resourceMap.getString("txtClass.text")); // NOI18N
        txtClass.setToolTipText(resourceMap.getString("txtClass.toolTipText")); // NOI18N
        txtClass.setName("txtClass"); // NOI18N
        txtClass.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                txtClassActionPerformed(evt);
            }
        });
        txtClass.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyTyped(java.awt.event.KeyEvent evt) {
                txtClassKeyTyped(evt);
            }
        });

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(com.aspect.snoop.JavaSnoop.class).getContext().getActionMap(AddFunctionHookView.class, this);
        btnBrowseForClass.setAction(actionMap.get("showChooseClassForm")); // NOI18N
        btnBrowseForClass.setText(resourceMap.getString("btnBrowseForClass.text")); // NOI18N
        btnBrowseForClass.setToolTipText(resourceMap.getString("btnBrowseForClass.toolTipText")); // NOI18N
        btnBrowseForClass.setFocusable(false);
        btnBrowseForClass.setName("btnBrowseForClass"); // NOI18N
        btnBrowseForClass.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnBrowseForClassActionPerformed(evt);
            }
        });

        jLabel2.setText(resourceMap.getString("jLabel2.text")); // NOI18N
        jLabel2.setName("jLabel2"); // NOI18N

        jScrollPane1.setName("jScrollPane1"); // NOI18N

        lstMethods.setModel(new javax.swing.AbstractListModel() {
            String[] strings = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            public int getSize() { return strings.length; }
            public Object getElementAt(int i) { return strings[i]; }
        });
        lstMethods.setName("lstMethods"); // NOI18N
        lstMethods.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyTyped(java.awt.event.KeyEvent evt) {
                lstMethodsKeyTyped(evt);
            }
        });
        jScrollPane1.setViewportView(lstMethods);

        btnAddWatch.setFont(resourceMap.getFont("btnAddWatch.font")); // NOI18N
        btnAddWatch.setText(resourceMap.getString("btnAddWatch.text")); // NOI18N
        btnAddWatch.setToolTipText(resourceMap.getString("btnAddWatch.toolTipText")); // NOI18N
        btnAddWatch.setFocusable(false);
        btnAddWatch.setName("btnAddWatch"); // NOI18N
        btnAddWatch.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnAddWatchActionPerformed(evt);
            }
        });

        chkShouldInherit.setText(resourceMap.getString("chkShouldInherit.text")); // NOI18N
        chkShouldInherit.setToolTipText(resourceMap.getString("chkShouldInherit.toolTipText")); // NOI18N
        chkShouldInherit.setFocusable(false);
        chkShouldInherit.setName("chkShouldInherit"); // NOI18N

        btnSearchForFunction.setAction(actionMap.get("searchForFunction")); // NOI18N
        btnSearchForFunction.setText(resourceMap.getString("btnSearchForFunction.text")); // NOI18N
        btnSearchForFunction.setToolTipText(resourceMap.getString("btnSearchForFunction.toolTipText")); // NOI18N
        btnSearchForFunction.setFocusable(false);
        btnSearchForFunction.setName("btnSearchForFunction"); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                        .addComponent(jLabel1)
                        .addGap(15, 15, 15)
                        .addComponent(txtClass, javax.swing.GroupLayout.DEFAULT_SIZE, 308, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnBrowseForClass))
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                        .addComponent(jLabel2)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addGap(4, 4, 4)
                                .addComponent(chkShouldInherit)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(btnSearchForFunction)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(btnAddWatch))
                            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 393, Short.MAX_VALUE))))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel1)
                    .addComponent(txtClass, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(btnBrowseForClass))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jLabel2)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(btnSearchForFunction)
                            .addComponent(chkShouldInherit)
                            .addComponent(btnAddWatch))))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void btnAddWatchActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAddWatchActionPerformed

        if (lstMethods.getSelectedIndex() != -1) {
            finalizeSelection();
            dispose();
        } else {
            JOptionPane.showMessageDialog(this, "Please select a method to hook!");
        }

    }//GEN-LAST:event_btnAddWatchActionPerformed

    private void btnBrowseForClassActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnBrowseForClassActionPerformed

        List<Class<?>> classes = SnoopAgent.getAgentManager().getLoadedClasses();

        ChooseClassView view = new ChooseClassView(this, classes);
        view.setVisible(true);

        UIUtil.waitForInput(view);

        if (view.getChosenClass() != null) {
            loadClassMethods(view.getChosenClass(), true);
        }

    }//GEN-LAST:event_btnBrowseForClassActionPerformed

    private void txtClassKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtClassKeyTyped

        String substring = txtClass.getText();

        if ( evt.getKeyChar() != java.awt.event.KeyEvent.VK_ENTER &&
             evt.getKeyChar() != java.awt.event.KeyEvent.VK_BACK_SPACE &&
             evt.getKeyChar() != java.awt.event.KeyEvent.VK_ESCAPE) {

            int pos = txtClass.getCaretPosition();

            if ( pos != 0 ) {
                String s1 = substring.substring(0, pos);
                String s2 = substring.substring(pos,txtClass.getText().length());
                substring = s1 + evt.getKeyChar() + s2;
            } else {
                substring += evt.getKeyChar();
            }
        }

        try {

            currentClass = SnoopAgent.getAgentManager().getFromAllClasses(substring);
            loadClassMethods(currentClass,false);

        } catch (ClassNotFoundException ex) {
            lstMethods.setListData(new String[0]);
        }

    }//GEN-LAST:event_txtClassKeyTyped

    private void txtClassActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtClassActionPerformed

        if ( lstMethods.getModel().getSize() > 0 ) {
            lstMethods.setSelectedIndex(0);
            lstMethods.requestFocus();
        }

    }//GEN-LAST:event_txtClassActionPerformed

    private void lstMethodsKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_lstMethodsKeyTyped
        if ( evt.getKeyChar() == java.awt.event.KeyEvent.VK_ENTER ) {
            if ( lstMethods.getSelectedIndex() != -1 ) {
                btnAddWatch.doClick();
            }
        }
    }//GEN-LAST:event_lstMethodsKeyTyped

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton btnAddWatch;
    private javax.swing.JButton btnBrowseForClass;
    private javax.swing.JButton btnSearchForFunction;
    private javax.swing.JCheckBox chkShouldInherit;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JList lstMethods;
    private javax.swing.JTextField txtClass;
    // End of variables declaration//GEN-END:variables

    private void finalizeSelection() {
        selectedClass = currentClass;
        selectedMethod = (AccessibleObject)lstMethods.getSelectedValue();
        parameterTypes = ReflectionUtil.getParameterTypes(selectedMethod);
        returnType = ReflectionUtil.getReturnType(selectedMethod);
        shouldInherit = chkShouldInherit.isSelected();

    }

    private void loadClassMethods(Class clazz, boolean setClassName) {

        currentClass = clazz;

        loadedMethods = currentClass.getDeclaredMethods();
        loadedConstructors = currentClass.getDeclaredConstructors();

        Member[] entries = new Member[loadedMethods.length + loadedConstructors.length];

        System.arraycopy(loadedMethods, 0, entries, 0, loadedMethods.length);
        System.arraycopy(loadedConstructors, 0, entries, loadedMethods.length, loadedConstructors.length);

        lstMethods.setListData(entries);

        if ( setClassName ) {
            txtClass.setText(currentClass.getName());
        }

        if ( ReflectionUtil.isInterfaceOrAbstract(currentClass)) {
            chkShouldInherit.setSelected(true);
            chkShouldInherit.setEnabled(false);
        } else {
            chkShouldInherit.setSelected(false);
            chkShouldInherit.setEnabled(true);
        }



    }

    @Action
    public void searchForFunction() {

        List<Class<?>> classes = SnoopAgent.getAgentManager().getLoadedClasses();

        FunctionSearchView view = new FunctionSearchView(this, true, classes);
        view.setVisible(true);

        UIUtil.waitForInput(view);

        AccessibleObject method = view.getMethodChosen();

        if ( method == null ) // indicates the user cancelled
            return;

        selectedClass = ReflectionUtil.getDeclaringClass(method);
        selectedMethod = method;
        parameterTypes = ReflectionUtil.getParameterTypes(method);
        shouldInherit = ReflectionUtil.isInterfaceOrAbstract(method);
        returnType = ReflectionUtil.getReturnType(method);

        dispose();

    }

    public Class getReturnType() {
        return returnType;
    }

}
